
// 三维向量
var Vec3 = function(x, y, z)
{
    this._m = new Float32Array(3);
    this.x = x;
    this.y = y;
    this.z = z;
};Vec3.prototype = 
{
    // Getters and Setters
    set x(x){ this._m[0] = x; },
    get x() { return this._m[0]; },
    set y(y){ this._m[1] = y; },
    get y() { return this._m[1]; },
    set z(z){ this._m[2] = z; },
    get z() { return this._m[2]; },
    set m(m){ this._m = m; },
    get m() { return this._m; },
    
    Copy : function(other)
    {
        this.m = new Float32Array(other.m);
    },
    
    Length : function()
    {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }
    ,
    Add : function(other)
    {
        return new Vec3(this.x + other.x, this.y + other.y, this.z + other.z);
    }
    ,
    Sub : function(other)
    {
        return new Vec3(this.x - other.x, this.y - other.y, this.z - other.z);
    }
    ,
    Mul : function(scale)
    {
        return new Vec3(this.x * scale, this.y * scale, this.z * scale);
    }
    ,
    Div : function(scale)
    {
        if(scale == 0.0)
            return new Vec3(0,0,0);
            
        return new Vec3(this.x / scale, this.y / scale, this.z / scale);
    }
    ,
    Dot : function(other)
    {
        return this.x * other.x + this.y * other.y + this.z * other.z;
    }
    ,
    Cross : function(other)
    {
        return new Vec3(this.y * other.z - this.z * other.y,
                        this.z * other.x - this.x * other.z,
                        this.x * other.y - this.y * other.x);
    }
    ,
    Normalize : function()
    {
        return this.Mul(1.0 / this.Length());
    }
    ,
    NormalizeSelf : function()
    {
        var invLen = 1.0 / this.Length();
        this.x *= invLen; this.y *= invLen;  this.z *= invLen; 
        return this;
    }
}

/*;Vec3.prototype.__defineSetter__('x', function(x){ this._m[0] = x; } );
Vec3.prototype.__defineGetter__('x', function() { return this._m[0];} );
Vec3.prototype.__defineSetter__('y', function(y){ this._m[1] = y; } );
Vec3.prototype.__defineGetter__('y', function() { return this._m[1];} );
Vec3.prototype.__defineSetter__('z', function(z){ this._m[2] = z; } );
Vec3.prototype.__defineGetter__('z', function() { return this._m[2];} );
Vec3.prototype.__defineSetter__('m', function(m){ this._m = m; } );
Vec3.prototype.__defineGetter__('m', function() { return this._m;} );*/

// 矩阵4x4
var Mat4x4 = function(m)
{
    if (m !== undefined){
        this._m = new Float32Array(m);
    }
    else{
        this._m = new Float32Array(16);
        this.Identity();
    }
};Mat4x4.prototype = 
{
    // Getters and Setters
    set m(m) { this._m = m; },
    get m()  { return this._m; },

    
    Copy : function(other)
    {
        this.m = new Float32Array(other.m)
    },
    
    Identity : function()
    {
        for (var i = 0; i < 16; ++i)
            this.m[i] = 0;
            
        this.m[0] = 1;
        this.m[5] = 1;
        this.m[10] = 1;
        this.m[15] = 1;
        return this;
    },
    
    LookAtRH : function(eyePos, lookAt, up)
    {
        this.m = LookAtRH(eyePos, lookAt, up);
        return this;
    },
    
    PerspectiveRH : function(fov_y, aspect, zNear, zFar)
    {
        this.m = PerspectiveRH(fov_y, aspect, zNear, zFar);
        return this;
    },
    
    Transpose : function()
    {
        this.m = MatrixTranspose(this.m);
        return this;
    },
    
    Multiply : function(other)
    {
        var m = MatrixMultiply(this.m, other.m);
        return new Mat4x4(m);
    },
    
    MultiplySelf : function(other)
    {
        this.m = MatrixMultiply(this.m, other.m);
        return this;
    },
    
    Translate : function(x, y , z)
    {
        this.m[3] += x;
        this.m[7] += y;
        this.m[11] += z;
        return this;
    },
    
    RotationX : function(angle)
    {
        this.m = MatrixRotationX(angle);
        return this;
    },
    
    RotationY : function(angle)
    {
        this.m = MatrixRotationY(angle);
        return this;
    },
    
    RotationZ : function(angle)
    {
        this.m = MatrixRotationZ(angle);
        return this;
    },
    
    Scaling : function(x, y, z)
    {
        this.m[0] *= x;
        this.m[5] *= y;
        this.m[10] *= z;
        return this;
    },
    
    Transform : function(point)
    {
        var p = new Float32Array(3);
        var m = this.m;
        p[0] = m[0]*point[0] + m[1] * point[1] + m[2] * point[2] + m[3];
        p[1] = m[4]*point[0] + m[5] * point[1] + m[6] * point[2] + m[7];
        p[2] = m[8]*point[0] + m[9] * point[1] + m[10] * point[2] + m[11];
        return p;
    },
    
    ToQuaternion : function()
    {
        var quat = Matrix4x4ToQuaternion(this.m);
        return new Quaternion(quat[0], quat[1], quat[2], quat[3])
    },
}

// 四元数
var Quaternion = function(x, y, z, w)
{
    this._m = new Float32Array(4);
    this.x = x;
    this.y = y;
    this.z = z;
    this.w = w;
};Quaternion.prototype = 
{
    // Getters and Setters
    set x(x){ this._m[0] = x; },
    get x() { return this._m[0]; },
    set y(y){ this._m[1] = y; },
    get y() { return this._m[1]; },
    set z(z){ this._m[2] = z; },
    get z() { return this._m[2]; },
    set w(w){ this._m[3] = w; },
    get w() { return this._m[3]; },
    set m(m){ this._m = m; },
    get m() { return this._m; },
    
    Length : function()
    {
        var len = this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
        return Math.sqrt(len);
    },
    
    Conjuqate : function()
    {
        return new Quaternion(-this.x, -this.y, -this.z, this.w);
    },
    
    ConjuqateSelf : function()
    {
        this.x = -this.x;
        this.y = -this.y;
        this.z = -this.z;
        
        return this;
    },
    
    Add : function(other)
    {
        var x = this.x + other.x;
        var y = this.y + other.y;
        var z = this.z + other.z;
        var w = this.w + other.w;
        
        return new Quaternion(x, y , z, w);
    },
    
    AddSelf : function(other)
    {
        this.x = this.x + other.x;
        this.y = this.y + other.y;
        this.z = this.z + other.z;
        this.w = this.w + other.w;
        
        return this;
    },
    
    Normalize : function()
    {
        var x = Math.sin(this.x);
        var y = Math.sin(this.y);
        var z = Math.sin(this.z);
        var w = Math.cos(this.w);
        return new Quaternion(x, y, z, w);
    },
    
    NormalizeSelf : function(vector3, theta)
    {
        this.x = Math.sin(vector3.x);
        this.y = Math.sin(vector3.y);
        this.z = Math.sin(vector3.z);
        this.w = Math.cos(vector3.w);
        return this;
    },
    
    Multiply : function(other)
    {
        var m = QuaternionMultiply(this.m, other.m);
        return new Quaternion(m[0],m[1],m[2],m[3]);
    },
    
    MultiplySelf : function(other)
    {
        this.m = QuaternionMultiply(this.m, other.m);
        return this;
    },
    
    ToMatrix4x4 : function()
    {
        if (this.Length() === 1.0){
            return new Mat4x4(QuaternionToMatrix4x4(this.m));
        }
        else{
            //var unitQuat = this.Normalize();
            //return new Mat4x4(QuaternionToMatrix4x4(unitQuat));
            return null;
        }
    },
    
    Slerp : function(other, t)
    {
        var m = QuaternionSlerp(this.m, other.m, t);
        return new Quaternion(m[0],m[1],m[2],m[3]);
    },
}

// 角度到弧度
function DegreesToRadians(degrees)
{
    return degrees * (Math.PI/180.0)
}

// 观察矩阵
function LookAtRH(eyePos, lookAt, up)
{
    var zaxis;
    var yaxis;
    var xaxis;

    zaxis = eyePos.Sub(lookAt);
    zaxis.NormalizeSelf();
    yaxis = up;
    yaxis.NormalizeSelf();
    xaxis = yaxis.Cross(zaxis);
    xaxis.NormalizeSelf();
    yaxis = zaxis.Cross(xaxis);
    yaxis.NormalizeSelf();

    var mat4x4 = new Float32Array(16);

    mat4x4[0] = xaxis.x; mat4x4[1] = xaxis.y; mat4x4[2] = xaxis.z;
    mat4x4[4] = yaxis.x; mat4x4[5] = yaxis.y; mat4x4[6] = yaxis.z;
    mat4x4[8] = zaxis.x; mat4x4[9] = zaxis.y; mat4x4[10] = zaxis.z;

    mat4x4[3] = -xaxis.Dot(eyePos);
    mat4x4[7] = -yaxis.Dot(eyePos);
    mat4x4[11] = -zaxis.Dot(eyePos);

    mat4x4[12] = 0.0;
    mat4x4[13] = 0.0;
    mat4x4[14] = 0.0;
    mat4x4[15] = 1.0;
    
    return mat4x4;
}

// 投影矩阵
function PerspectiveRH(fov_y, aspect, zNear, zFar)
{
    if (zNear > zFar)
        Swap(zNear, zFar);

    var mat4x4 = new Float32Array(16);
    for (var i = 0; i < 16; ++i)
        mat4x4[i] = 0;

    var n_f = 1.0 / (zNear - zFar);
    var yScale = 1.0/ Math.tan(DegreesToRadians(fov_y) * 0.5);
    var xScale = yScale / aspect;

    mat4x4[0] = xScale;
    mat4x4[5] = yScale;
    mat4x4[10] = (zFar + zNear) * n_f;
    mat4x4[14] = -1.0;
    mat4x4[11] = 2.0 * zFar * zNear * n_f;
    return mat4x4;
}

// 矩阵转置
function MatrixTranspose(mat4x4)
{
    var newMat = new Float32Array(16);
    for (var i = 0; i < 4; ++i)
        for(var j = 0; j < 4; ++j)
            newMat[i*4+j] = mat4x4[j*4+i];
        
    return newMat;
}

// 矩阵乘
function MatrixMultiply(mat1, mat2)
{
    var row = 0
    var newMat = new Float32Array(16);
    for (var i = 0; i < 4; ++i){
        for(var j = 0; j < 4; ++j){
            row = i * 4
            newMat[row+j] = mat1[row  ] * mat2[j  ] + 
                            mat1[row+1] * mat2[4+j] + 
                            mat1[row+2] * mat2[8+j] + 
                            mat1[row+3] * mat2[12+j];
        }
    }
                 
    return newMat;
}

// 矩阵迹
function MatrixTrace(mat4x4)
{
    var trace = 0
    for (var i = 0; i < 4; ++i){
        trace += mat4x4[i*4+i];
    }
    return trace;
}


function MatrixRotationX(angle)
{
    var rad = DegreesToRadians(angle);
    var cos = Math.cos(rad);
    var sin = Math.sin(rad);
    var newMat = new Float32Array(16);
    newMat[0]  = 1.0; newMat[1]  = 0.0; newMat[2]  = 0.0; newMat[3]  = 0.0;
    newMat[4]  = 0.0; newMat[5]  = cos; newMat[6]  =-sin; newMat[7]  = 0.0;
    newMat[8]  = 0.0; newMat[9]  = sin; newMat[10] = cos; newMat[11] = 0.0;
    newMat[12] = 0.0; newMat[13] = 0.0; newMat[14] = 0.0; newMat[15] = 1.0;
    return newMat;
}

function MatrixRotationY(angle)
{
    var rad = DegreesToRadians(angle);
    var cos = Math.cos(rad);
    var sin = Math.sin(rad);
    var newMat = new Float32Array(16);
    newMat[0]  = cos; newMat[1]  = 0.0; newMat[2]  = sin; newMat[3]  = 0.0;
    newMat[4]  = 0.0; newMat[5]  = 1.0; newMat[6]  = 0.0; newMat[7]  = 0.0;
    newMat[8]  =-sin; newMat[9]  = 0.0; newMat[10] = cos; newMat[11] = 0.0;
    newMat[12] = 0.0; newMat[13] = 0.0; newMat[14] = 0.0; newMat[15] = 1.0;
    return newMat;
}

function MatrixRotationZ(angle)
{
    var rad = DegreesToRadians(angle);
    var cos = Math.cos(rad);
    var sin = Math.sin(rad);
    var newMat = new Float32Array(16);
    newMat[0]  = cos; newMat[1]  =-sin; newMat[2]  = 0.0; newMat[3]  = 0.0;
    newMat[4]  = sin; newMat[5]  = cos; newMat[6]  = 0.0; newMat[7]  = 0.0;
    newMat[8]  = 0.0; newMat[9]  = 0.0; newMat[10] = 1.0; newMat[11] = 0.0;
    newMat[12] = 0.0; newMat[13] = 0.0; newMat[14] = 0.0; newMat[15] = 1.0;
    return newMat;
}

// 四元数乘
function QuaternionMultiply(q1, q2)
{
    var quat = new Float32Array(4);
    quat[0] = q1[3]*q2[0] + q1[0]*q2[3] + q1[1]*q2[2] - q1[2]*q2[1];
    quat[1] = q1[3]*q2[1] - q1[0]*q2[2] + q1[1]*q2[3] + q1[2]*q2[0];
    quat[2] = q1[3]*q2[2] + q1[0]*q2[1] - q1[1]*q2[0] + q1[2]*q2[3];
    quat[3] = q1[3]*q2[3] - q1[0]*q2[0] - q1[1]*q2[1] - q1[2]*q2[2];

    return quat;
}

// spherical linear interpolation
// 球面线性插值
function QuaternionSlerp(q1, q2, t)
{
    //                sin(delta*(1-t))        sin(delta*t)
    //slerp(q,r,t) = ---------------- * q +  --------------
    //                sin(delta)              sin(delta)
    // delta = acos(q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w)
    
    var quat = new Float32Array(4);
    if(t === 0.0){
        quat[0] = q1[0];
        quat[1] = q1[1];
        quat[2] = q1[2];
        quat[3] = q1[3];
        return quat;
    }else if(t === 1.0){
        quat[0] = q2[0];
        quat[1] = q2[1];
        quat[2] = q2[2];
        quat[3] = q2[3];
        return quat;
    }

    
    var p = q1[0] * q2[0] + q1[1] * q2[1] + q1[2] * q2[2] + q1[3] * q2[3];
    if(Math.abs(p) >= 1.0){
        quat[0] = q2[0];
        quat[1] = q2[1];
        quat[2] = q2[2];
        quat[3] = q2[3];
        return quat;
    }
    
    if(p < 0.0){
        quat[0] = -q2[0];
        quat[1] = -q2[1];
        quat[2] = -q2[2];
        quat[3] = -q2[3];
        p = -p;
    }else{
        quat[0] = q2[0];
        quat[1] = q2[1];
        quat[2] = q2[2];
        quat[3] = q2[3];
    }
    
    var delta = Math.acos(p)
    var sinDelta = Math.sqrt(1.0 - p*p);
    
    var r1 = Math.sin((1 - t) * delta) / sinDelta;
    var r2 = Math.sin(t * delta) / sinDelta;
    quat[0] = (q1[0] * r1 + quat[0] * r2);
    quat[1] = (q1[1] * r1 + quat[1] * r2);
    quat[2] = (q1[2] * r1 + quat[2] * r2);
    quat[3] = (q1[3] * r1 + quat[3] * r2);
    return quat;
}

// 四元数转矩阵
function QuaternionToMatrix4x4(quat)
{
    // the quat must is unit quaternion. otherwise  s = 2 / quat.Length()
    var x = quat[0];
    var y = quat[1];
    var z = quat[2];
    var w = quat[3];
    
    var s = 2.0;
    var mat4x4 = new Float32Array(16);
    mat4x4[0] = 1.0 - s*(y*y + z*z);    mat4x4[1] = s*(x*y - w*z);          mat4x4[2] = s*(w*y + x*z);         mat4x4[3] = 0.0;
    mat4x4[4] = s*(x*y + w*z),          mat4x4[5] = 1.0 - s*(x*x + z*z);    mat4x4[6] = s*(y*z - w*x);         mat4x4[7] = 0.0;
    mat4x4[8] = s*(x*z - w*y);          mat4x4[9] = s*(y*z + w*x);          mat4x4[10] = 1.0 - s*(x*x + y*y);  mat4x4[11] = 0.0;
    mat4x4[12] = 0.0;                   mat4x4[13] = 0.0;                   mat4x4[14] = 0.0;                   mat4x4[15] = 1.0;
    
    return mat4x4;
}

// 矩阵转四元数
function Matrix4x4ToQuaternion(mat4x4)
{
    var quat = new Float32Array(4);
    quat[3] = 0.5 * Math.sqrt(MatrixTrace(mat4x4))
    var w_x_4 = 4.0 * quat[3];
    quat[0] = (mat4x4[9] - mat4x4[6]) / w_x_4;
    quat[1] = (mat4x4[2] - mat4x4[8]) / w_x_4;
    quat[2] = (mat4x4[4] - mat4x4[1]) / w_x_4;
    return quat;
}

// 线性插值
function InterpolationLinear(a, b, factor)
{
    if(a.length == 3){
        var data = new Float32Array(3)

        data[0] = (1.0 - factor) * a[0] + factor * b[0];
        data[1] = (1.0 - factor) * a[1] + factor * b[1];
        data[2] = (1.0 - factor) * a[2] + factor * b[2];
        return data;
    }else{
        return (1 - factor) * a + factor * b;
    }
}

// hermite插值
function InterpolationHermite(v1, v2, c1, c2, factor)
{
    var factorPow2 = factor * factor;

    var factor1 = factorPow2 * (2.0 * factor - 3.0) + 1.0;   // 2*t^3 - 3*t^2 + 1.0f
    var factor2 = factorPow2 * (factor - 2.0) + factor;        // t^3 - 2*t^2 + t
    var factor3 = factorPow2 * (factor - 1.0);                 // t^3 - t^2
    var factor4 = factorPow2 * (3.0 - 2.0 * factor);          // -2*t^3 + 3*t^2

    if(v1.length == 3){
        // p(t) = (2*t^3 - 3*t^2 + 1)*p0 + (t^3 - 2*t^2 + t)*m0 + (-2*t^3 + 3*t^2)*p1 + (t^3 - t^2)*m1
        var data = new Float32Array(3)
        data[0]  = factor1 * v1[0] + factor2 * c1[0] + factor3 * c2[0] + factor4 * v2[0];
        data[1]  = factor1 * v1[1] + factor2 * c1[1] + factor3 * c2[1] + factor4 * v2[1];
        data[2]  = factor1 * v1[2] + factor2 * c1[2] + factor3 * c2[2] + factor4 * v2[2];
        return data;
    }else{
        return factor1 * v1 + factor2 * c1 + factor3 * c2 + factor4 * v2;
    }
}

// bezier插值
function InterpolationBezier(v1, v2, c1, c2, factor)
{
    var factorPow2 = factor * factor;
    var inverseFactor = 1 - factor;
    var inverseFactorPow2 = inverseFactor * inverseFactor;
    var factor1 = inverseFactorPow2 * inverseFactor;            // (1 - t)^3
    var factor2 = 3.0 * factor * inverseFactorPow2;             // 3*(1 - t)^2
    var factor3 = 3.0 * factorPow2 * inverseFactor;             // 3*(1 - t)*t^2
    var factor4 = factorPow2 * factor;                          // t^3

    if(v1.length == 3){
        // p(t) = (1 - t)^3*p0 + 3*(1 - t)^2*m0 + 3*(1 - t)*t^2*m1 + (t^3)*p1
        var data = new Float32Array(3)
        data[0] = factor1 * v1[0] + factor2 * c1[0] + factor3 * c2[0] + factor4 *v2[0];
        data[1] = factor1 * v1[1] + factor2 * c1[1] + factor3 * c2[1] + factor4 *v2[1];
        data[2] = factor1 * v1[2] + factor2 * c1[2] + factor3 * c2[2] + factor4 *v2[2];
        return data;
    }else{
        return factor1 * v1 + factor2 * c1 + factor3 * c2 + factor4 * v2;
    }
}

/**
 * Gets the closest power of two bigger than the given number.
 *
 * @param {number} x
 * @returns {number} A power of two number.
 */
Math.powerOfTwo = function (x) {
    x--;
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    x++;

    return x;
};

/**
 * Gets the decimal part of the given number.
 *
 * @param {number} x
 * @returns {number} decimal part of the given number.
 */
Math.fract = function(x) {
    if(x >= 0){
        return x - Math.floor(x);
    }else{
        return x - Math.ceil(x);
    }
}

/**
 * Gets a random number in the given range.
 *
 * @param {number} a
 * @param {number} b
 * @returns {number} A random number in [a, b].
 */
Math.randomRange = function (a, b) {
    return a + Math.random() * (b - a);
};